﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using Fetches.Fetch.Fetcher;
using Fetches.Fetch.Fetcher.LinkExtractor;
using Fetches.Fetch.Utils;
using Fetches.Fetch.Utils.FileHosting;
using Newtonsoft.Json;

namespace Fetches.Fetch.Dto
{
    #region FetchOrder

    /// <summary>
    ///     使用者的操作就是创建爬取任务工单，交给系统去执行就可以了
    ///     专注Fetch，Process另行处理
    ///     TODO:FetchOrder应该实现版本化，这样方便以后添加修改字段不至于序列化失败
    /// </summary>
    public class FetchOrder : IJsonWriteable
    {
        #region static

        private static readonly HashSet<int> DefaultStatusCodeSet = new HashSet<int>();

        static FetchOrder()
        {
            DefaultStatusCodeSet.Add(200);
        }


        public static FetchOrder Create(string name)
        {
            var order = new FetchOrder
            {
                Name = name
            };
            return order;
        }

        #endregion

        #region Fetcher

        /// <summary>
        ///     辅助记忆的工单名称
        /// </summary>
        public string Name { get; set; }

        //原计划使用GUID，实际操作感觉使用类的全名更方便
        //使用Guid的话设置Fetcher类的GUID Attribute，然后使用typeof(AbstractFetcher).GUID获取
        public string FetcherTypeName { get; set; }

        /// <summary>
        ///     Factory加减速调整FetcherCount
        /// </summary>
        public int FetcherCount { get; set; } = 1;

        /// <summary>
        ///     Fetch失败重试次数
        /// </summary>
        public int RetryTimes { get; set; } = 3;

        /// <summary>
        ///     遇到反爬手段的失败重试次数
        /// </summary>
        public int AntiRetryTimes { get; set; } = 3;

        #endregion

        #region 任务信息

        /// <summary>
        ///     默认只接受HttpStatus.OK
        /// </summary>
        public HashSet<int> AcceptStatCode { get; set; } = DefaultStatusCodeSet;

        /// <summary>
        ///     任务中的链接具有亲缘性,需要由当前Fetcher继续处理
        /// </summary>
        public bool LinkIsRelative { get; set; }

        /// <summary>
        ///     种子链接
        /// </summary>
        public IList<string> SeedUrls { get; set; } = new List<string>();

        public IList<string> Domains { get; set; }

        public string UserAgent { get; set; } =
            "Fetches:elastic framework for spider DevOps,written in .Net Standard 2.0";

        public string Charset { get; set; }
        public string ContentType { get; set; } = "text/html;charset=UTF-8";
        public bool UseGzip { get; set; } = true;

        /// <summary>
        ///     额外信息，用于比如小说爬取时提供小说ID等操作
        /// </summary>
        public Dictionary<string, object> Extra { get; set; } = new Dictionary<string, object>();

        #endregion

        #region 频率控制

        public int RequestTimeout { get; set; } = 10000;
        public int IdleSleepTime { get; set; } = 5000;
        public int FetchInterval { get; set; } = 1000;
        public int RetrySleepTime { get; set; } = 3000;

        #endregion

        #region LinkExtractor规则

        /// <summary>
        ///     为了序列化所以set没有限制为private，实际不应该允许外部设置
        /// </summary>
        public string LinkExtractor { get; set; }

        public string LinkExtractorRule { get; set; }

        /// <summary>
        ///     默认使用正则表达式自动发现链接,SpawnUrl为False情况下不使用LinkExtractor,需要Fetcher自行Push新Request
        /// </summary>
        public bool SpawnUrl { get; set; } = true;

        /// <summary>
        ///     后续Robots.txt也由Extractor来管理
        /// </summary>
        public bool IgnoreRobots { get; set; } = true;

        public int Depth { get; set; }
        public List<string> IndexPatterns { get; set; } = new List<string>();
        public List<string> ContentPatterns { get; set; } = new List<string>();

        #endregion

        #region Proxy

        public bool UseProxy { get; set; }
        /// <summary>
        /// 是每个Fetcher获得一个Proxy还是多个Fetcher共用Proxy
        /// 有时候Proxy不是因为目标站屏蔽，而是为了翻墙，共用也没关系
        /// </summary>
        public bool ProxyEachFetcher { get; set; }
        public string ProxyTestUri { get; set; } = "http://www.baidu.com";
        public string ProxySupplierCfg { get; set; }

        #endregion

        #region FileHosting
        public IList<string> HostingTypeList = new List<string>();
        public bool HostingRelated2Fetcher { get; set; }

        #endregion

        #region 链式设置

        #region Fetcher

        /// <summary>
        ///     Factory的加减速设置会调整FetcherCount，这里只是设置初始值
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public FetchOrder SetFetcherCount(int count)
        {
            FetcherCount = count;
            return this;
        }

        /// <summary>
        ///     失败重试次数
        /// </summary>
        /// <param name="retryTimes"></param>
        /// <returns></returns>
        public FetchOrder SetRetryTimes(int retryTimes)
        {
            RetryTimes = retryTimes;
            return this;
        }

        /// <summary>
        ///     遇到反爬时的重试次数
        /// </summary>
        /// <param name="retryTimes"></param>
        /// <returns></returns>
        public FetchOrder SetAntiRetryTimes(int retryTimes)
        {
            AntiRetryTimes = retryTimes;
            return this;
        }
        #endregion

        #region 任务信息

        /// <summary>
        ///     不会重试的Http状态码
        /// </summary>
        /// <param name="acceptStatCode"></param>
        /// <returns></returns>
        public FetchOrder SetAcceptStatCode(HashSet<int> acceptStatCode)
        {
            AcceptStatCode = acceptStatCode;
            return this;
        }

        /// <summary>
        ///     链接需要绑定到特定Fetcher
        /// </summary>
        /// <param name="relative"></param>
        /// <returns></returns>
        public FetchOrder SetLinkIsRelative(bool relative = true)
        {
            LinkIsRelative = relative;
            return this;
        }

        /// <summary>
        ///     种子链接默认视为索引页
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public FetchOrder AddSeedUrl(string url)
        {
            SeedUrls.Add(url);
            return this;
        }

        /// <summary>
        ///     种子链接默认视为索引页
        /// </summary>
        /// <param name="urls"></param>
        /// <returns></returns>
        public FetchOrder AddSeedUrls(IList<string> urls)
        {
            foreach (var url in urls)
            {
                SeedUrls.Add(url);
            }

            return this;
        }

        public FetchOrder SetDomains(IList<string> domains)
        {
            Domains = domains;
            return this;
        }

        public FetchOrder SetUserAgent(string userAgent)
        {
            UserAgent = userAgent;
            return this;
        }

        /// <summary>
        ///     默认情况下自动识别编码
        /// </summary>
        /// <param name="charset"></param>
        /// <returns></returns>
        public FetchOrder SetCharset(string charset)
        {
            Charset = charset;
            return this;
        }

        public FetchOrder SetContentType(string contentType)
        {
            ContentType = contentType;
            return this;
        }

        public FetchOrder SetUseGzip(bool useGzip)
        {
            UseGzip = useGzip;
            return this;
        }

        #endregion

        #region 频率控制

        public FetchOrder SetRequestTimeout(int timeout)
        {
            RequestTimeout = timeout;
            return this;
        }

        public FetchOrder SetIdleSleepTime(int time)
        {
            IdleSleepTime = time;
            return this;
        }

        public FetchOrder SetFetchInterval(int time)
        {
            FetchInterval = time;
            return this;
        }

        public FetchOrder SetRetrySleepTime(int retrySleepTime)
        {
            RetrySleepTime = retrySleepTime;
            return this;
        }

        #endregion

        #region LinkExtractor

        /// <summary>
        ///     Extractor不允许随意设置，避免问题。需要由页面加工产生特定链接的建议定制Fetcher来实现，在页面下载后解析直接Push到Scheduler
        /// </summary>
        /// <returns></returns>
        public FetchOrder UseDefaultExtractor()
        {
            return UseLinkExtractor(new PatternMatchExtractor());
        }

        /// <summary>
        ///     从api返回的json数据中抽取后续地址
        /// </summary>
        /// <param name="extractor"></param>
        /// <returns></returns>
        public FetchOrder UseJsonExtractor(JsonExtractor extractor)
        {
            return UseLinkExtractor(extractor);
        }

        /// <summary>
        ///     QueryExtractor用于实现原来JustCrawler中ExactSite那种只抽取指定区域链接并转换的效果
        /// </summary>
        /// <param name="extractor"></param>
        /// <returns></returns>
        public FetchOrder UseQueryExtractor(QueryExtractor extractor)
        {
            return UseLinkExtractor(extractor);
        }

        private FetchOrder UseLinkExtractor(ILinkExtractor extractor)
        {
            LinkExtractor = extractor.GetType().FullName;
            LinkExtractorRule = extractor.GetInitializeRule();
            return this;
        }

        public FetchOrder AddIndexPattern(string pattern)
        {
            IndexPatterns.Add(pattern);
            return this;
        }

        public FetchOrder AddContentPattern(string pattern)
        {
            ContentPatterns.Add(pattern);
            return this;
        }

        /// <summary>
        ///     不自动从新页面中获取链接
        /// </summary>
        /// <returns></returns>
        public FetchOrder DisableSpawnUrl()
        {
            SpawnUrl = false;
            return this;
        }

        public FetchOrder EnableSpawnUrl()
        {
            SpawnUrl = true;
            return this;
        }

        #endregion

        #region Proxy
        /// <summary>
        /// 
        /// </summary>
        /// <param name="proxySupplierCfg"></param>
        /// <param name="proxyTestUri"></param>
        /// <param name="proxyEachFetcher">各fetcher使用独立的proxy</param>
        /// <returns></returns>
        public FetchOrder EnableProxy(string proxySupplierCfg = null, string proxyTestUri = null,
            bool proxyEachFetcher = false)
        {
            UseProxy = true;
            ProxyEachFetcher = proxyEachFetcher;
            ProxySupplierCfg = proxySupplierCfg;
            ProxyTestUri = proxyTestUri;
            return this;
        }

        public FetchOrder DisableProxy()
        {
            UseProxy = false;
            return this;
        }

        #endregion

        #region ExtraKV

        public FetchOrder SetExtraKv(string key, object value)
        {
            Extra[key] = value;
            return this;
        }

        public T GetExtraKv<T>(string key)
        {
            if (!Extra.ContainsKey(key))
            {
                return default(T);
            }
            var data = Extra[key];
            var convertor = TypeDescriptor.GetConverter(data);
            return (T)convertor.ConvertTo(data, typeof(T));
        }

        public T GetExtraKvJson<T>(string key)
        {
            var data = GetExtraKv(key);
            return JsonConvert.DeserializeObject<T>(data);
        }

        public string GetExtraKv(string key)
        {
            if (!Extra.ContainsKey(key))
            {
                return String.Empty;
            }
            var data = Extra[key];
            return data.ToString();
        }

        #endregion

        #region FileHosting

        public FetchOrder EnableFileHosting(IReadOnlyList<FileType> hostingFileTypes, bool related2Fetcher = false)
        {
            foreach (var hostingFileType in hostingFileTypes)
            {
                HostingTypeList.Add(hostingFileType.ToString());
            }

            HostingRelated2Fetcher = related2Fetcher;
            return this;
        }

        #endregion

        #endregion
    }

    #endregion

    #region FetchOrder<AbstractFetcher>

    /// <summary>
    ///     FetchOrder的Fetcher类型要么定制的时候指定，要么Fetch的时候指定，必须由Fetcher类型
    ///     针对部分网站使用多种浏览器可以通过定制Fetcher中包含多种浏览器对象，自行决定用哪个浏览器
    /// </summary>
    /// <typeparam name="T">指定Fetcher类型</typeparam>
    public class FetchOrder<T> : FetchOrder where T : AbstractFetcher
    {
        public FetchOrder()
        {
            FetcherTypeName = typeof(T).FullName;
        }
    }

    #endregion
}